/*
尽可能还原 Promise 中的每一个 API, 并通过注释的方式描述思路和原理.
*/


// promise核心关键点
// 1、promise类的参数是一个function，且立即执行（放到构造函数里面立即执行）
// 2、promise有三个状态：等待、成功、失败（可用常量定义）
// 3、promise状态变更，只有两种情况：（变更前判断状态）
// （1）等待=>成功
// （2）等待=>失败
// 4、resolve()、reject()用来改变状态、赋值、执行回调函数；（注意使用箭头函数）
// 5、then()方法核心功能
// （1）then()方法里每种场景都需要考虑同步、异步执行场景
// （2）根据promise状态来执行回调函数（单个then调用场景）
//   （2.1）成功状态 => 执行成功回调函数（同步情况，顺序执行代码，resolve、reject在then之前）
//   （2.2）失败状态 => 执行失败回调函数（同步情况，顺序执行代码，resolve、reject在then之前）
//   （2.3）等待状态 => 将回调函数赋值，放到resolve、reject里面去执行（异步调用情况下，resolve、reject比then后执行）
// （3）then()可以被同一个实例多次调用
// （4）then()可以被链式调用，then的返回值始终为promise对象
// （5）then()可以被链式调用，回调函数的返回值有三种情况
//   （5.1）promise对象，调用then将value传给resolve
//   （5.2）普通值，直接resolve
//   （5.3）无返回值，和普通值一样直接resolve，value为undefined
//   （5.4）sucessCallback、failCallback、甚至finally的回调函数都要考虑返回值传递给下一个then
// （6）then()被调用时的参数为两个回调函数sucessCallback、failCallback：
//   （6.1）执行sucessCallback(value)时，传递数据value
//   （6.2）执行failCallback(error)时，传递数据error
//   （6.3）如果then()调用时没有填参数，需要主动构造参数：value=>value，相当于then(value=>value)
// （7）then()、finally()的后面还可以继续调用then()（因为他们的返回值始终为promise对象，不管回调函数里面返回值是多少都可以）
//  
// 

// 状态常量
const PENDING = "pending";
const SUCESSED = "sucessed";
const FAILED = "failed";

class MyPromise{
    constructor(fn){
        try{
            // fn作为MyPromise构造函数的参数
            // new MyPromise时需要立即执行函数fn，所以在构造函数里面执行fn
            fn(this.resolve, this.reject);
        }catch(e){
            this.reject(e);
        }
    }
    // 状态
    status = PENDING;
    // 成功、失败回调函数参数
    value = undefined;
    error = undefined;
    // 成功、失败回调函数，考虑链式调用场景需要用数组
    sucessCallback = [];
    failCallback = [];

    // 调用resolve时直接调用，避免this指向错误使用箭头函数
    resolve = value => {
        // 如果状态不是等待，不再继续执行
        if(this.status !== PENDING) return;
        this.status = SUCESSED;
        this.value = value;
        // 执行所有then方法的成功回调函数
        while(this.sucessCallback.length){
            this.sucessCallback.shift()()
        }
    }

    // 调用reject时直接调用，避免this指向错误使用箭头函数
    reject = error => {
        // 如果状态不是等待，不再继续执行
        if(this.status !== PENDING) return;
        this.status = FAILED;
        this.error = error;
        // 执行所有then方法的失败回调函数
        while(this.failCallback.length){
            this.failCallback.shift()()
        }
    }

    // 调用then时为实例对象，可以不使用箭头函数
    // 同一个promise对象的then方法可以多次调用
    then(sucessCallback, failCallback) {
        // 考虑then函数没有传参的情况
        sucessCallback = sucessCallback ? sucessCallback : value => value
        failCallback = failCallback ? failCallback : error => { throw error }

        // then函数要有返回值，始终为promise对象
        let promise2 = new MyPromise((resolve,reject)=>{
            // 根据状态分别执行回调函数
            // 成功的状态，将返回值传递给下一个then
            if(this.status === SUCESSED){
                setTimeout(() => {
                    try{
                        // 将成功回调的返回值赋值给下一个then的成功回调函数，有三种情况
                        let x = sucessCallback(this.value)
                        resolveValue(x,promise2,resolve,reject)
                    }
                    catch(e){
                        reject(e)
                    }
                },0)
            }
            // 失败的状态
            else if(this.status === FAILED){
                setTimeout(() => {
                    try{
                        // 将成功回调的返回值赋值给下一个then的成功回调函数，有三种情况
                        let x = failCallback(this.error)
                        resolveValue(x,promise2,resolve,reject)
                    }
                    catch(e){
                        reject(e)
                    }
                },0)
            }
            // 异步情况下，then比callback先执行；需要在resolve、reject中再次执行callback
            // 等待的状态(异步情况下，需要将两函数放到resolve、reject中执行)
            else{
                this.sucessCallback.push(()=>{
                    setTimeout(() => {
                        try{
                            let x = sucessCallback(this.value)
                            resolveValue(x,promise2,resolve,reject)
                        }
                        catch(e){
                            reject(e)
                        }
                    }, 0)
                })
                this.failCallback.push(()=>{
                    setTimeout(() => {
                        try{
                            let x = failCallback(this.error)
                            resolveValue(x,promise2,resolve,reject)
                        }
                        catch(e){
                            reject(e)
                        }
                    }, 0)
                })
            }
        });

        return promise2;
    }

    catch(callback){
        this.then(undefined,error=>callback(error))
    }

    // 不管前面是走到哪个回调里面，始终都会执行一次finally
    // finally后面还可以继续链式调用then，且then方法拿到的值是上一个调用finally的对象的返回值（意思是调不调用finally，then应该都是一样的）
    // finally里面可以return 一个promise对象，且会等待它执行结束
    // callback的返回值可以是值，也可以是promise对象；索性使用resolve()包装一下
    finally(callback){

        return this.then(value => {
            return MyPromise.resolve(callback()).then(() => value)
        },error => {
            return MyPromise.resolve(callback()).then(() => { throw error })
        })
    }

    // 1、入参为一个array数组
    // 2、返回值为一个Promise对象
    // 3、通过resolve传递数据给下一个then的sucessCallback，是一个结果数组
    // 4、需要判断所有异步操作执行完毕
    static all(array){
        return new MyPromise((resolve,reject)=>{
            let results = []
            let index = 0

            function addItem (i,value){
                results[i] = value
                index++

                // 使用index标记是否所有的异步操作都执行完毕
                // 如果所有操作都成功，最后一个异步操作时index应该等于数组array长度
                if(index === array.length){
                    resolve(results)
                }
            }

            for(let i=0;i<array.length;i++){
                // 判断是否Promise对象
                if(array[i] instanceof MyPromise){
                    array[i].then((value)=>{
                        // results.push(value) 不能用push，要提前对应元素的index和value
                        addItem(i,value)
                    },reject)
                }
                else{
                    addItem(i,array[i])
                }
                
            }
        })
    }

    // 1、参数为Promise对象，直接返回
    // 2、参数为普通值，返回一个Promise包装的对象，resolve(参数)传给下一个then的sucessCallback
    static resolve(x){
        if(x instanceof MyPromise){
            return x
        }
        else{
            return new MyPromise((resolve)=>{
                resolve(x);
            })
        }
    }
}

// 将成功回调的返回值赋值给下一个then的成功回调函数，有三种情况：
//（1）返回值为promise对象，调用then将vlue传给resolve
//（2）返回值为普通值，直接resolve
//（3）无返回值，和普通值一样直接resolve，value为undefined
function resolveValue(x, promise2, resolve, reject){
    if(x === promise2){
        return reject(new TypeError("ERROR 存在循环引用"))
    }

    if(x instanceof MyPromise){
        x.then(value=>resolve(value),error=>reject(error))
    }
    else{
        resolve(x)
    }
}

module.exports = MyPromise;



// 遗留问题：
// 1、catch的问题：不能链式catch？即末尾的catch不能抓到中间的error？
// 2、finally的问题：return promise时，后面的then拿到的是哪个数据？
